package com.yunao.commons.serachcore.core;


import com.yunao.commons.serachcore.core.bean.*;
import com.yunao.commons.serachcore.core.exception.SearchException;
import com.yunao.commons.serachcore.core.help.Mode;
import com.yunao.commons.serachcore.core.query.Query;
import com.yunao.commons.serachcore.utils.JedisUtils;
import com.yunao.commons.serachcore.utils.lock.RedisLock;
import org.springframework.util.DigestUtils;
import redis.clients.jedis.Jedis;

import java.io.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.function.Function;

class RedisCacheIsearch extends IsearchImpl implements Isearch {
	
	private Serializer serializer;
	
	public RedisCacheIsearch() {
		this.serializer = new Serializer();
	}

	@Override
	public Long count(Query query) throws SearchException {
		return super.count(query);
	}

	@Override
	public Articles query(Query query) throws SearchException {
		return (Articles) searchCache(query, IsearchType.QUERY, t -> {
			try {
				return super.query(t);
			} catch (SearchException e) {
				e.printStackTrace();
				return null;
			}
		});
	}

	@Override
	public Groups group(Query query) throws SearchException {
		return (Groups) searchCache(query, IsearchType.GROUP, t -> {
			try {
				return super.group(t);
			} catch (SearchException e) {
				e.printStackTrace();
				return null;
			}
		});
	}

	@Override
	public Cubes cube(Query query) throws SearchException {
		return (Cubes) searchCache(query, IsearchType.CUBE, t -> {
			try {
				return super.cube(t);
			} catch (SearchException e) {
				e.printStackTrace();
				return null;
			}
		});
	}

	@Override
	public Wordclouds wordcloud(Query query) throws SearchException {
		return (Wordclouds) searchCache(query, IsearchType.WORDCLOUD, t -> {
			try {
				return super.wordcloud(t);
			} catch (SearchException e) {
				e.printStackTrace();
				return null;
			}
		});
	}

	@Override
	public Wordrelations wordrelation(Query query) throws SearchException {
		return (Wordrelations) searchCache(query, IsearchType.WORDRELATION, t -> {
			try {
				return super.wordrelation(t);
			} catch (SearchException e) {
				e.printStackTrace();
				return null;
			}
		});
	}

	@Override
	public Clusters cluster(Query query) throws SearchException {
		return (Clusters) searchCache(query, IsearchType.CLUSTER, t -> {
			try {
				return super.cluster(t);
			} catch (SearchException e) {
				e.printStackTrace();
				return null;
			}
		});
	}

	@Override
	public Articles distinct(Query query) throws SearchException {
		return (Articles) searchCache(query, IsearchType.DISTINCT, t -> {
			try {
				return super.distinct(t);
			} catch (SearchException e) {
				e.printStackTrace();
				return null;
			}
		});
	}
	
	private BaseEntities searchCache(Query query, IsearchType iType, Function<Query, BaseEntities> f) throws SearchException {
		Lock lock = new RedisLock(getKey(query, iType));
		try {
			lock.lock();
			return searchCacheUnlock(query, iType, f);
		} finally {
			lock.unlock();
		}
	}
	
	private BaseEntities searchCacheUnlock(Query query, IsearchType iType, Function<Query, BaseEntities> f) throws SearchException {
		BaseEntities res;
		
		byte[] key = String.valueOf(iType).concat(getKey(query, iType)).getBytes();
		try (Jedis jedis = JedisUtils.getResource()) {
			if (jedis.exists(key)) {
				res = (BaseEntities) serializer.deserialize(jedis.get(key));
			} else {
				res = f.apply(query);
				if (res != null) {
					jedis.set(key, serializer.serialize(res));
					long seconds = query.getMode() == Mode.FRESH
							| query.getMode() == Mode.READ
							? TimeUnit.HOURS.toSeconds(12)
							: TimeUnit.MINUTES.toSeconds(15);
					jedis.expire(key, (int) seconds);
				}
			}
		}
		return res;
	}

	private static class Serializer {

		public byte[] serialize(Object object) throws SearchException {
			try {
				ByteArrayOutputStream byteArrayOutput = new ByteArrayOutputStream();
				ObjectOutputStream objectOutput = new ObjectOutputStream(byteArrayOutput);
				objectOutput.writeObject(object);
				return byteArrayOutput.toByteArray();
			} catch (IOException e) {
				throw new SearchException("Serializer error");
			}
		}

		public Object deserialize(byte[] bytes) throws SearchException {
			try {
				ByteArrayInputStream byteArrayInput = new ByteArrayInputStream(bytes);
				ObjectInputStream objectInput = new ObjectInputStream(byteArrayInput);
				return objectInput.readObject();
			} catch (ClassNotFoundException | IOException e) {
				throw new SearchException("Serializer error");
			}
		}

	}
	
	private String getKey(Query query, IsearchType iType) throws SearchException {
		byte[] typeBytes = String.valueOf(iType).getBytes();
		byte[] queryBytes = serializer.serialize(query);
		final byte[] bytes = new byte[typeBytes.length + queryBytes.length];
		System.arraycopy(typeBytes, 0, bytes, 0, typeBytes.length);
        System.arraycopy(queryBytes, 0, bytes, typeBytes.length, queryBytes.length);
        return DigestUtils.md5DigestAsHex(bytes);
	}
	
}

